MongoDB Master:MongoDB“诟病”分析

        近期MongoDB在Hack News上是频繁中枪。许多人更是声称恨上了MongoDB,David mytton就在他的博客中揭露了MongoDB许多现存问题。然而恨的人有之偏爱的也同样很多,作为回击:Russell Smith带来了多年工作经验的总结。Russell Smith曾担任Ops和大型网站缩放顾问并且帮助过Guardian、Experian等多家公司,MongoDB London User Group的联合创始人。作为MongoDB Master(MongoDB官方认可的MongoDB核心贡献者组织,并通过社区分享自己的专业技术),其参与工作的基础设施单服务器每秒查询超过3万次,每天活跃数据更在1TB以上。

        下面来看Russell对MongoDB一些常见及生僻的问题做出分析:

32位 vs 64位

        现在大多数的服务器都对32位操作系统实现支持,更有许多新型硬件支持着允许更多RAM的64位操作系统。

        MongoDB也同时发布了32位及64位两个版本的数据库。归结于MongoDB使用的内存映射文件,32位版本只支持2G数据的存储。对于标准的Replica Set,MongoDB只拥有单一的处理策略 —— mongod。如果你想在未来储存2G以上的数据,请使用64位版本的MongoDB。如果拥有分片安装,那么32位版本同样可以使用。

        总结:使用64位版本或者理解32位版本的限制。

文件大小限制

        不同于RDBMS把数据储存在行与列中,MongoDB的数据是储存在文件中的。这些文件使用二进制存储形式,其格式为类似JSON格式的BSON格式。

        和其它的数据库一样,单个文件的储存大小是有限制的。在旧版本的MongoDB中,单个文件都限制在4M以内。而新版本的MongoDB单文件已经支持到16M大小。这样的限制也许是令人厌烦的,但是10gen的意见是:如果这项设置不停的困扰到你,那么是否你的设计模式存在着问题;或者你可以使用文件无大小限制的GridFS。

        这种情况通常的建议是避免存储过大的文件,不定期的更新数据库中存储的各种对象。而像Amazon S3或者Rackspace Cloudfiles这样的服务通常可能会是更好的选择,而非必要情况下最好别让基础设施陷入过载。

        总结:把每个文件保持在16M以下,那么一切都好。

写入失败

        MongoDB在默认的情况下允许高速的写入和更新,而付出的代价就是没有明确的错误通知。默认情况下多数的驱动都在做异步、“不安全”写入 —— 这就意味着驱动程序不能立即反馈错误信息,类似于MySQL的INSERT DELAYED。如果你想知道某个事情是否成功,你必须使用getLastError手动的检查错误信息。

        某些情况下如果你需要在错误发生后立刻得到错误信息,即:大多数的驱动中都很容易实现同步“安全”查询。这将谋杀掉MongoDB不同于传统数据库的优点。

        如果对比“完全安全”的同步写入你需要多一点性能,同时还想要一定程度的安全,那么你可以使用getLastError with‘j’让MongoDB只到一份日志提交后再发出错误报告通知。那么日志将以100毫秒一次的速度输出到磁盘,而不是60秒。

        总结:如果必须要写入确认,你可以使用安全写入或getLastError。

数据结构模型的弱化不等于没有数据结构模型

        RDBMS一般都拥有一个预定义的数据结构模型:表格的行和列,每个字段都拥有名称和数据类型。如果你想给其中一行加一列,那么你必须给整个表格都添加一列。

        MongoDB则是移除了这个设置,对于Collection和文件没有强制的模型限定。这有益于快速开发及简易修改。
  当然这不意味着你就可以无视结构模型的设计,一个合适的结构模型可以让你获得MongoDB的最佳性能。赶快阅读MongoDB文档,或者观看这些结构模型设计的相关视频吧!

  Schema Design Basics
  Schema Design at Scale
  Schema Design Principles and Practice

        总结:设计结构模型并充分利用MongoDB的特色。

默认情况下修改语句修改的只是单个文件

        在传统的RDBMS中除非使用LIMIT子句,修改语句作用的将是所有匹配的地方。然而MongoDB每个查询上都默认使用等价“LIMIT 1”的设置。虽然无法做到“LIMIT 5”,但是你可以通过下面的语句整个的移除限制:

1
db.people.update({age: {$gt: 30}}, {$set: {past_it: true}}, false, true)

        同样在官方的驱动中还有类似的选项 —— ‘multi’。

        总结:可以通过指定多个文件的multi为true来完成多文件修改

查询区分大小写

        字符串的查询可能不按预期的那样发展 —— 这归结于MongoDB默认区分大小写。

        例如:db.people.find({name: ‘Russell’})与db.people.find({name: ‘ russell‘})是不同的。在这里最理想的解决方案就是对需要查询数据进行确认。你也可以通过正则表达式进行查询,比如:db.people.find({name:/Russell/i}),但是这样会影响到性能。

        总结:查询是区分大小写的,在牺牲速度的情况下可以利用正则表达式。

对输入的数据无容错性

        当你尝试向传统数据库插入错误类型的数据,传统的数据库一般会把数据转换成预定义的类型。然而这在MongoDB中是行不通的,因为MongoDB的文件是没有预定义数据模型的。这样的话MongoDB会插入你输入的任何数据。

        总结:使用准确的数据类型。

关于锁

        当资源被代码的多个部分所共享时,需要确信锁必须要确保这处资源只能在一个地方被操作。

        旧版本的MongoDB (pre 2.0)拥有一个全局的写入锁。这就意味贯穿整个服务器中只有一个地方做写操作。这就可能导致数据库因为某个地方锁定超负载而停滞。这个问题在2.0版本中的得到了显著的改善,并且在当前2.2版本中得到了进一步的加强。MongoDB 2.2使用数据库级别的锁在这个问题上迈进了一大步。同样值得期待的Collection级别的锁也计划在下一个版本中推出。

        尽管如此,Russell还是认为:大多数受此限制的应用程序于其说是受MongoDB影响,还不如说是程序本身的问题来的更直接。

        总结:使用最新的稳定版本才能获得最高的性能。

关于包

        在类Ubuntu和Debian系统上安装时,许多人都出现过“过时版本”这样的问题。解决方案很简单:使用10gen官方库,那么在Ubuntu和Debian上安装也会像在Fedora和Centos上安装一样流畅。

        总结:使用拥有大多数最新版本的官方包。